import React, { ReactNode, useEffect, useRef, useState } from "react";

type MasonryPops = React.ComponentPropsWithoutRef<"div"> & {
  /**列数，支持媒体查询 */
  columns: { default: number; [key: number]: number };
  /**间距 */
  gap: number;
  children: ReactNode[];
  /**检测子元素高度变化，而更新布局，会带来性能开销*/
  cellObserver?: boolean;
};

export const Masonry: React.FC<MasonryPops> = (props) => {
  const { children, columns, gap, cellObserver, style, ...nest } = props;
  const _columns = useMediaColumns(columns);
  return (
    <div
      {...nest}
      style={{
        display: "grid",
        alignItems: "start",
        gridAutoRows: 1, //一个网格高占1px
        gridColumnGap: gap, //列间距
        gridTemplateColumns: `repeat(${_columns}, minmax(0, 1fr))`,
        ...style,
      }}
    >
      {children.map((c, idx) => (
        <Cell key={idx} gap={gap} cellObserver={cellObserver}>
          {c}
        </Cell>
      ))}
    </div>
  );
};

function useMediaColumns(medias: MasonryPops["columns"]) {
  const { default: def, ..._medias } = medias;
  const [value, setValue] = useState(getValue());

  function getValue() {
    const __medias = { 0: def, ..._medias } as Omit<typeof medias, "default">;

    const __medias_keys = Object.keys(__medias)
      .filter((w) => matchMedia(`(min-width: ${w}px)`).matches)
      .map((w) => Number(w));

    const __medias_key = Math.max(...__medias_keys);

    return __medias[__medias_key];
  }

  useEffect(() => {
    const mediaQueries = Object.keys(_medias).map((w) =>
      matchMedia(`(min-width: ${w}px)`)
    );

    const onSizeChange = () => {
      setValue(getValue());
    };

    for (const mediaQuery of mediaQueries) {
      mediaQuery.addEventListener("change", onSizeChange);
    }

    return () => {
      for (const mediaQuery of mediaQueries) {
        mediaQuery.removeEventListener("change", onSizeChange);
      }
    };
  }, [medias]);

  return value;
}

type CellProps = React.PropsWithChildren<
  Pick<MasonryPops, "gap" | "cellObserver">
>;

const Cell: React.FC<CellProps> = (props) => {
  const { children, gap, cellObserver } = props;
  const ref = useRef<HTMLDivElement>(null);
  useEffect(() => {
    const observer = new ResizeObserver((entries, observer) => {
      for (const entry of entries) {
        if (entry.contentRect.height) {
          const target = entry.target as HTMLDivElement;
          const height = Math.ceil(entry.contentRect.height);
          target.style.gridRowEnd = `span ${height + gap}`;
          setTimeout(() => {
            target.style.opacity = "1";
            if (!cellObserver) {
              observer.disconnect();
            }
          });
        }
      }
    });

    // 监听目标元素
    observer.observe(ref.current!);
    return () => {
      observer.disconnect();
    };
  }, []);

  return (
    <div ref={ref} style={{ opacity: 0 }}>
      {children}
    </div>
  );
};
